#include "UdpSocket.hpp"
#include "hiScopeGuard.h"
#include <string.h>
#include <uv.h>
#include "DepLibUV.hpp"
#include <cstring>
#include<stdio.h>
#include "common/HiCommonLog.hpp"

static int GetAddressInfo(const struct sockaddr *addr, int *family, std::string &ip, uint16_t *port);


/* Static methods for UV callbacks. */

inline static void onAlloc(uv_handle_t *handle, size_t suggestedSize, uv_buf_t *buf) {   
	HI_FUNC_DEBUG();
	UdpSocket* udp =  static_cast<UdpSocket *>(handle->data);
	if (nullptr == udp) {
		return;
	}
	udp->OnUVHandleAlloc(buf);
}

inline static void onRecv(uv_udp_t *handle, ssize_t nread, const uv_buf_t *buf,
                          const struct sockaddr *addr, unsigned int flags) {  
	HI_DEBUG("UdpSocket.cpp onRecv: called, len:%d\n", nread);
	if (nullptr == handle || nullptr == handle->data) {  
		printf("UdpSocket.cpp handle->data is nullptr\n");
		return;
	}
	UdpSocket* udp =  static_cast<UdpSocket *>(handle->data);
	if (nullptr == udp) {
		HI_DEBUG("UdpSocket.cpp udp is nullptr\n");
		return;
	}
	udp->OnUvRecv(nread, buf, addr, flags);
}

int UdpSocket::Listen(const char* ip, uint16_t port, const CALL_BACK& onReceiveData) {
	return this->Listen(ip, port, onReceiveData, default_OnClose, default_OnSendFailed);
}

int UdpSocket::Listen(const char* ip, uint16_t port, const CALL_BACK& onReceiveData, const ON_CLOSE& onClose, const ON_SEND_FAILED& onSendFailed) {	
	this->localIP = ip;
	this->localPort = port;
	this->receiveDataCallBack = onReceiveData;
	this->onCloseCallBack = onClose;
	this->onSendFailed = onSendFailed;
	this->isClosing = false;
	
	this->uvHandle = new uv_udp_t;
	this->uvHandle->data = (void*)this;
	HI_DEBUG("test1\n");
	if (!DepLibUV::GetLoop()) {
		HI_DEBUG("test2\n");
		return -1;
	}
	
	int err = uv_udp_init(DepLibUV::GetLoop(), this->uvHandle);	
	if (err != 0) {	
		delete this->uvHandle;
		this->uvHandle = nullptr;
		return err;
	}
	HiScopeGuard scope_inited_uvHandle([&](){	
		uv_close(reinterpret_cast<uv_handle_t*>(this->uvHandle), static_cast<uv_close_cb>(UdpSocket::onClose));
	});
	// clang-format off
	struct sockaddr_storage bindAddr{};
	// clang-format on

	//if (AF_INET != Utils::IP::GetFamily(ip)) {
	//	return -1;
	//}
	
	err = uv_ip4_addr(ip, static_cast<int>(port), reinterpret_cast<struct sockaddr_in *>(&bindAddr));
	if (err != 0) {
		HI_DEBUG("uv_ip4_addr failed, error:%s\n", uv_strerror(err));
		return err;
	}	
	
	int flags = 0;
	err = uv_udp_bind(this->uvHandle, reinterpret_cast<const struct sockaddr *>(&bindAddr), flags);
	if (err != 0) {
		HI_DEBUG("uv_udp_bind failed, error:%s\n", uv_strerror(err));
		return err;
	}

	uv_alloc_cb alloc_cb = static_cast<uv_alloc_cb>(::onAlloc);
	if (nullptr == alloc_cb) {
		HI_DEBUG("uv_udp_recv_start failed, alloc_cb is nullptr\n");
		return err;
	}
	
	uv_udp_recv_cb receive_cb = static_cast<uv_udp_recv_cb>(::onRecv);
	if (nullptr == receive_cb) {
		HI_DEBUG("uv_udp_recv_start failed, receive_cb is nullptr\n");
		return err;
	}
	
	err = uv_udp_recv_start(this->uvHandle, alloc_cb, receive_cb);
	if (err != 0) {
		HI_DEBUG("uv_udp_recv_start failed, error:%s\n", uv_strerror(err));
		return err;
	}

	// Set local address.
	err = this->SetLocalAddress();
	if (err != 0) {
		return err;
	}
	
	scope_inited_uvHandle.dismissed();
	isClosing = false;
	HI_DEBUG("start listen udp:%s:%d\n", this->localIP.c_str(), this->localPort);
	HI_DEBUG("----active loop handles----\n");
	uv_print_active_handles(DepLibUV::GetLoop(), stdout);
	HI_DEBUG("----all loop handles----\n");
	uv_print_all_handles(DepLibUV::GetLoop(), stdout);
	DepLibUV::RunLoop();
	
	return 0;
}

int UdpSocket::SetLocalAddress() {
	int len = sizeof(this->localAddr);
	int err = uv_udp_getsockname(this->uvHandle, reinterpret_cast<struct sockaddr *>(&this->localAddr),	 &len);
	if (err != 0) {
		return err;
	}
	HI_DEBUG("uv_udp_getsockname success\n");

	int family;
	::GetAddressInfo(
	  reinterpret_cast<const struct sockaddr *>(&this->localAddr), &family,
	  this->localIP, &this->localPort);
	return 0;
}

void UdpSocket::OnUVHandleAlloc(uv_buf_t *buf) {
	if (nullptr == buf) {
		return;
	}
	// Tell UV to write into the static buffer.
	buf->base = reinterpret_cast<char*>(this->readBuffer);
	// Give UV all the buffer space.
	buf->len = READ_BUFFER_SIZE;
}

inline void UdpSocket::OnUvRecv(ssize_t nread, const uv_buf_t *buf,
                                const struct sockaddr *addr,
                                unsigned int flags) {
	HI_DEBUG("UdpSocket::OnUvRecv receive message, len:%d\n", nread);
	if (this->isClosing) {
		HI_DEBUG("UdpSocket::OnUvRecv is Closing\n");
		return;
	}

	// NOTE: libuv calls twice to alloc & recv when a datagram is received, the
	// second one with nread = 0 and addr = NULL. Ignore it.
	if (nread == 0) {
		HI_DEBUG("UdpSocket::OnUvRecv nread == 0\n");
		return;
	}

	// Check flags.
	if ((flags & UV_UDP_PARTIAL) != 0u) {
		HI_DEBUG("UdpSocket::OnUvRecv (flags & UV_UDP_PARTIAL) != 0u\n");
		return;
	}

	// Data received.
	if (nread < 0) {
		// MS_DEBUG_DEV("read error: %s", uv_strerror(nread));
	}
	
	// Update received bytes.
	this->recvBytes += nread;

	// Notify the subclass.
	receiveDataCallBack(reinterpret_cast<uint8_t *>(buf->base), nread, addr);
}

static int GetAddressInfo(const struct sockaddr *addr, int *family, std::string &ip, uint16_t *port) {
	if (addr->sa_family != AF_INET) {
		return -1;
	}

	char ipBuffer[256] = {0};
	int err = uv_inet_ntop(AF_INET, &((struct sockaddr_in*)addr)->sin_addr, ipBuffer, INET_ADDRSTRLEN);

	if (err != 0) {
		return err;
	}

	*port = static_cast<uint16_t>(ntohs(((struct sockaddr_in*)addr)->sin_port));

	*family = addr->sa_family;
	ip.assign(ipBuffer);
	return 0;
}
